import {PerspectiveCamera, Vector2, Vector3} from "three";
import {CRS} from "../crs/CRS";
import {LatLngBounds} from "../geom/LatLngBounds";
import {LatLng} from "../geom/LatLng";
import {FreeMap} from "../";

/**
 * 地图摄像机
 */
export class MapCamera extends PerspectiveCamera {

    protected crs: CRS;

    protected map: FreeMap;

    protected projectCenter: Vector2;

    public mapZoom: number;

    public maxZoom: number;

    public minZoom: number;

    public width: number;

    public height: number;

    constructor(container: HTMLElement, crs: CRS, map: FreeMap) {
        super(50, 1.0, 0.1, 2000);

        this.crs = crs;
        this.map = map;

        this.init(container);
    }

    /**
     * 初始化地图相机参数
     */
    protected init(container: HTMLElement) {
        this.position.z = this.far / 2;
        this.updateProjectionMatrix();
        this.projectCenter = this.crs.projection(this.map.config.center);

        this.initZoom();

        this.initWH(container);

        this.initFov();
    }

    /**
     * 计算比例尺
     * 米/像素
     */
    public getResolution(zoom: number = this.mapZoom): number {
        let r = 6378137;
        let size = 256;
        return 2 * Math.PI * r / (Math.pow(2, zoom) * size)
    }

    //设置fov
    private initFov() {
        this.fov = this.getFov(this.mapZoom);
        this.updateProjectionMatrix();
    }

    //设置缩放等级
    private initZoom() {
        this.minZoom = 25;
        this.maxZoom = 0;
        this.mapZoom = this.maxZoom;
    }

    /**
     * 设置宽高
     * @param {HTMLElement} container
     */
    protected initWH(container: HTMLElement) {
        this.width = container.offsetWidth;
        this.height = container.offsetHeight;
        this.aspect = this.width / this.height;
        this.updateProjectionMatrix();
    }

    /**
     * 屏幕点转经纬度
     */
    public screenPointToLatLng(x: number, y: number): LatLng {
        let r = this.getResolution();

        let dx = this.projectCenter.x + this.position.x + x * r;
        let dy = this.projectCenter.y + this.position.y + y * r;

        let latLng = this.crs.unProjection(new Vector2(dx
            , dy));

        return latLng.reSize();
    }

    /**
     * 经纬度转屏幕坐标
     * @param {LatLng} latLng
     * @param width
     * @param height
     * @returns {Vector2}
     */
    public LatLngToScreenPoint(latLng: LatLng, width = this.width, height = this.height): Vector2 {
        let projectedLatLng = this.crs.projection(latLng);

        this.normalizePoint(projectedLatLng);

        let point = new Vector3(projectedLatLng.x, projectedLatLng.y, 0);

        let projectedPoint = point.project(this);

        let halfWidth = width / 2;
        let halfHeight = height / 2;

        return new Vector2(Math.round(projectedPoint.x * halfWidth + halfWidth),
            Math.round(-projectedPoint.y * halfHeight + halfHeight));
    }

    public getFov(zoom: number = this.mapZoom): number {
        let resolution = this.getResolution(zoom);

        let fov = Math.atan2(resolution * this.height / 2, this.position.z)
            * 2 * (180 / Math.PI);

        return fov;
    }

    public zoomBy(dz: number) {
        this.zoomTo(this.mapZoom + dz);
    }

    public zoomTo(zoom: number) {
        if (zoom > this.minZoom || zoom < this.maxZoom) {
            return
        }

        let lastZoom = Number(this.mapZoom);

        this.mapZoom = zoom;

        this.fov = this.getFov(zoom);

        this.updateProjectionMatrix();

        this.map.mapRenderer.render();

        this.map.eventDispatcher.dispatchEvent({
            zoom: zoom,
            lastZoom: lastZoom,
            type: "zoomchange"
        });
    }

    public moveTo(latLng: LatLng) {
        let vec = this.crs.projection(latLng);

        this.normalizePoint(vec);

        this.position.x = vec.x;
        this.position.y = vec.y;

        this.map.mapRenderer.render()
    }

    public moveBy(x: number, y: number) {
        this.position.x += x;
        this.position.y += y;

        this.map.mapRenderer.render();
    }

    public getScreenBounds(): LatLngBounds {
        let southWest = this.screenPointToLatLng(-this.width / 2, -this.height / 2);

        let northEast = this.screenPointToLatLng(this.width / 2, this.height / 2);

        return new LatLngBounds(southWest, northEast);
    }

    public normalizePoint(point: Vector2) {
        point.x = point.x - this.projectCenter.x;
        point.y = point.y - this.projectCenter.y;
    }

    public unNormalizePoint(point: Vector2) {
        point.x = point.x + this.projectCenter.x;
        point.y = point.y + this.projectCenter.y;
    }

    public projection(latLng: LatLng): Vector2 {
        let centerVec = this.map.crs.projection(latLng);

        this.map.mapRenderer.camera.normalizePoint(centerVec);

        return centerVec;
    }

    public unProjection(vec: Vector2): LatLng {
        this.unNormalizePoint(vec);

        return this.crs.unProjection(vec);
    }
}
